package com.comboo.example.web.aop;

import com.comboo.example.web.api.RequestWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author Sherman
 * created in 2018/12/19 2018
 */
@Slf4j
@Component
@Aspect
public class MyHandlerRequestLogger {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.DeleteMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PatchMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    private void handlerMethod() {
    }

    @Before("handlerMethod()")
    public void doBefore(JoinPoint jp) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        if (request == null) {
            // 非请求类型的调用（例如直接调用controller方法），不进行参数信息提取。
            return;
        }
        try {
            doRequestLog(request, jp);
        } catch (Exception ex) {
            log.warn("日志切面出现问题，导致无法正常记录日志。请检查异常（见下文）。本异常已被捕获，不会影响方法本身的调用", ex);
        }
    }

    private void doRequestLog(HttpServletRequest request, JoinPoint jp) throws JsonProcessingException {

        Map<String, Object> map = new HashMap<>();
        map.put("客户端地址", logClientAddr(request));
        map.put("请求地址", logRequestDestAddr(request));
        map.put("解析后参数", logArgsJson(jp));
        map.put("headers", logHeaders(request));
        log.info("请求内容：\n" + OBJECT_MAPPER.writeValueAsString(map));
    }

    private String logClientAddr(HttpServletRequest request) {
        return request.getRemoteHost();
    }

    private String logRequestDestAddr(HttpServletRequest request) {
        return request.getRequestURL().toString();
    }

    private Map<String, Object> logArgsJson(JoinPoint jp) throws JsonProcessingException {
        Object[] args = jp.getArgs();
        Map<String, Object> argsMap = new HashMap<>();
        for (Object obj : args) {
            if (obj instanceof RequestWrapper) {
                RequestWrapper wrapper = (RequestWrapper) obj;
                argsMap.put(((RequestWrapper) obj).getPayload().getClass().getSimpleName().toLowerCase(), wrapper.getPayload());
            }
            argsMap.put(obj.getClass().getSimpleName().toLowerCase(), obj);
        }
        return argsMap;
    }

    private Map<String, String> logHeaders(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        Enumeration<String> allHeaders = request.getHeaderNames();
        if (allHeaders != null) {
            Map<String, String> headerArgs = new HashMap<>();
            while (allHeaders.hasMoreElements()) {
                String header = allHeaders.nextElement();
                // 不记录敏感信息
                if ("cookie".equalsIgnoreCase(header)) {
                    headerArgs.put("cookie", "*********");
                } else if ("authorization".equalsIgnoreCase(header)) {
                    headerArgs.put("authorization", "*********");
                } else {
                    String headerValue = request.getHeader(header);
                    headerArgs.put(header, headerValue);
                }
            }
            return headerArgs;
        }
        return null;
    }

}
